﻿/**
 * Copyright (C) 2013 original author and authors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NGuice.Inject.Spi;
using NGuice.Inject.Internal.Util;
using System.Reflection;

namespace NGuice.Inject.Internal
{
    /// <summary>
    /// C#泛型没有？通配符，使用标记接口解决泛型通配问题
    /// </summary>
    public interface ConstructorInjector { }

    /// <summary>
    /// 使用一个可注入构造器创建实例。在构造之后，所有可注入域和方法都将被注入
    /// </summary>
    internal sealed class ConstructorInjector<T> : ConstructorInjector
    {
        private readonly ImmutableSet<InjectionPoint> injectableMembers;
        private readonly SingleParameterInjector[] parameterInjectors;
        private readonly ConstructionProxy<T> constructionProxy;
        private readonly MembersInjectorImpl<T> membersInjector;

        internal ConstructorInjector(ISet<InjectionPoint> injectableMembers, ConstructionProxy<T> constructionProxy,
                                      SingleParameterInjector[] parameterInjectors, MembersInjectorImpl<T> membersInjector)
        {
            this.injectableMembers = ImmutableSet<InjectionPoint>.CopyOf<InjectionPoint, InjectionPoint>(injectableMembers);
            this.constructionProxy = constructionProxy;
            this.parameterInjectors = parameterInjectors;
            this.membersInjector = membersInjector;
        }

        public ImmutableSet<InjectionPoint> InjectableMembers
        {
            get
            {
                return injectableMembers;
            }
        }

        internal ConstructionProxy<T> ConstructionProxy
        {
            get
            {
                return constructionProxy;
            }
        }

        /// <summary>
        /// 构造一个实例。返回一个对象来替代T是因为返回的对象可能是一个代理
        /// </summary>
        /// <param name="errors"></param>
        /// <param name="context"></param>
        /// <returns></returns>
        /// <exception cref="ErrorsException"></exception>
        internal object Construct(Errors errors, InternalContext context, Type expectedType, bool allowProxy)
        {
            ConstructionContext<T> constructionContext = context.GetConstructionContext<T>(this);

            // We have a circular reference between constructors. Return a proxy.
            if (constructionContext.IsConstructing)
            {
                if (!allowProxy)
                {
                    throw errors.CircularProxiesDisabled(expectedType).ToException();
                }
                else
                {
                    // TODO (crazybob): if we can't proxy this object, can we proxy the other object?
                    return constructionContext.CreateProxy(errors, expectedType);
                }
            }

            // If we're re-entering this factory while injecting fields or methods,
            // return the same instance. This prevents infinite loops.
            T t = constructionContext.CurrentReference;
            if (t != null)
            {
                return t;
            }

            try
            {
                // First time through...
                constructionContext.StartConstruction();
                try
                {
                    object[] parameters = SingleParameterInjector<T>.GetAll(errors, context, parameterInjectors);
                    t = constructionProxy.NewInstance(parameters);
                    constructionContext.SetProxyDelegates(t);
                }
                finally
                {
                    constructionContext.FinishConstruction();
                }

                // Store reference. If an injector re-enters this factory, they'll get the same reference.
                constructionContext.CurrentReference = t;

                membersInjector.InjectMembers(t, errors, context, false);
                membersInjector.NotifyListeners(t, errors);

                return t;
            }
            catch (TargetInvocationException userException)
            {
                Exception cause = userException.InnerException != null
                    ? userException.InnerException
                    : userException;
                throw errors.WithSource(constructionProxy.InjectionPoint)
                    .ErrorInjectingConstructor(cause).ToException();
            }
            finally
            {
                constructionContext.RemoveCurrentReference();
            }
        }
    }
}
